package org.budo.mybatis.dao.statement.builder.impl;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Transient;

import org.apache.ibatis.session.Configuration;
import org.budo.mybatis.dao.entity.configer.MyBatisDaoEntityConfiger;
import org.budo.mybatis.dao.statement.builder.StatementBuilderConsts;
import org.budo.mybatis.dao.statement.builder.util.StatementBuilderUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.hibernate.annotations.Formula;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * @author lmw
 */
@Getter
@Setter
@NoArgsConstructor
public class ResultMapBuilder implements StatementBuilderConsts {
    private static final boolean HAS_CLASS_FORMULA = ReflectUtil.hasClass("org.hibernate.annotations.Formula");

    private MyBatisDaoEntityConfiger entityConfiger;

    /**
     * 最大级联深度
     */
    private Integer associationMaxDepth = 1;

    // true 有注解的才要
    // false 没有注解的也要
    private Boolean annotation = true;;

    public ResultMapBuilder(MyBatisDaoEntityConfiger entityConfiger, Integer associationMaxDepth) {
        this.setEntityConfiger(entityConfiger);
        this.setAssociationMaxDepth(associationMaxDepth);
    }

    /**
     * 不包括 updateable=false 的只读属性
     */
    public synchronized String getResultMapId_Basic(Configuration configuration, Class<?> entityType) {
        String id = entityType.getName() + ".$ResultMap";

        if (!configuration.hasResultMap(id)) {
            String statementXml = "";
            statementXml += _4 + "<resultMap  id=\"$ResultMap\" type=\"" + entityType.getName() + "\">\n";

            List<String> resultMapColumns = this.resultMapColumns(entityType, "", "", false, null);
            for (String resultMapColumn : resultMapColumns) {
                statementXml += _8 + resultMapColumn;
            }

            statementXml += _4 + "</resultMap>";
            StatementBuilderUtil.parseXmlMapper(configuration, entityType.getName(), statementXml);
        }

        return id;
    }

    /**
     * 包括 updateable=false 的只读属性
     */
    public synchronized String getResultMapId_All(Configuration configuration, Class<?> entityType) {
        String id = entityType.getName() + ".$ResultMap$All";
        if (!configuration.hasResultMap(id)) {
            String statementXml = "";
            statementXml += _4 + "<resultMap  id=\"$ResultMap$All\" type=\"" + entityType.getName() + "\">\n";

            List<String> resultMapColumns = this.resultMapColumns(entityType, "", "", true, null);
            for (String resultMapColumn : resultMapColumns) {
                statementXml += _8 + resultMapColumn;
            }

            statementXml += _4 + "</resultMap>";
            StatementBuilderUtil.parseXmlMapper(configuration, entityType.getName(), statementXml);
        }

        return id;
    }

    /**
     * 组装resultMap的 column id association
     * 
     * @param entityType
     * @param linePrefix
     * @param columnPrefix
     * @param includeReadOnlyColumn 是否包含只读属性
     */
    private List<String> resultMapColumns(Class<?> entityType, String linePrefix, String columnPrefix, Boolean includeReadOnlyColumn, JoinColumn joinColumn) {
        List<String> resultMapColumns = new ArrayList<String>();
        String idColumn = this.getEntityConfiger().getIdColumn(entityType);
        if (!StringUtil.isEmpty(idColumn)) {
            if (null == joinColumn || StringUtil.isEmpty(joinColumn.name())) {
                resultMapColumns.add(linePrefix + "<id property=\"" + idColumn + "\" column=\"" + columnPrefix + this.getEntityConfiger().getIdFieldName(entityType) + "\"/>\n");
            } else {
                resultMapColumns.add(linePrefix + "<id property=\"" + idColumn + "\" column=\"" + joinColumn.name() + "\"/>\n");
            }
        }

        List<Field> fields = ReflectUtil.getAllFields(entityType);
        for (Field field : fields) {
            // 不需要注解
            if (null != this.getAnnotation() && !this.getAnnotation()) {
                String columnName = this.getEntityConfiger().getColumnName(field) //
                        .replace("`", ""); // 替换

                resultMapColumns.add(linePrefix + "<result property=\"" + field.getName() + "\" column=\"" + columnPrefix + columnName + "\"/>\n");
                continue;
            }

            // 有 @Column
            Column column = field.getAnnotation(Column.class);
            if (null != column && (includeReadOnlyColumn || column.updatable())) { // 判断只读属性
                String columnName = this.getEntityConfiger().getColumnName(field) //
                        .replace("`", ""); // 替换

                resultMapColumns.add(linePrefix + "<result property=\"" + field.getName() + "\" column=\"" + columnPrefix + columnName + "\"/>\n");
                continue;
            }

            // 有 @Formula
            if (HAS_CLASS_FORMULA && null != field.getAnnotation(Formula.class) && includeReadOnlyColumn) {
                String columnName = this.getEntityConfiger().getColumnName(field) //
                        .replace("`", ""); // 替换

                resultMapColumns.add(linePrefix + "<result property=\"" + field.getName() + "\" column=\"" + columnPrefix + columnName + "\"/>\n");
                continue;
            }

            // 有 @Transient
            if (null != field.getAnnotation(Transient.class) && includeReadOnlyColumn && ReflectUtil.isBasicType(field.getType())) {
                String columnName = this.getEntityConfiger().getColumnName(field) //
                        .replace("`", ""); // 替换

                resultMapColumns.add(linePrefix + "<result property=\"" + field.getName() + "\" column=\"" + columnPrefix + columnName + "\"/>\n");
                continue;
            }
        }

        // 两次循环是为了保证顺序，association 在 result 后面
        for (Field field : fields) {
            if (!ReflectUtil.isBasicType(field.getType()) // 不是基本类型
                    && !field.getType().isArray() // 不是数组
                    && !Map.class.isAssignableFrom(field.getType()) // 不是集合属性
                    && !Collection.class.isAssignableFrom(field.getType()) // 不是集合属性
                    && (null != field.getAnnotation(OneToOne.class) // 有这三个注解中的一个
                            || null != field.getAnnotation(ManyToOne.class) //
                            || null != field.getAnnotation(Transient.class)) //
                    && !StringUtil.startsWith(linePrefix, StringUtil.dup(_4, this.getAssociationMaxDepth()))) { // 最多级联多少级,更多的就不要了
                resultMapColumns.add(linePrefix + "<association property=\"" + field.getName() + "\" javaType=\"" + field.getType().getName() + "\">\n");

                JoinColumn fieldJoinColumn = field.getAnnotation(JoinColumn.class);
                String fieldColumnPrefix = columnPrefix + field.getName() + "_";
                String fieldLinePrefix = _4 + linePrefix;
                List<String> associationColumns = this.resultMapColumns(field.getType(), fieldLinePrefix, fieldColumnPrefix, includeReadOnlyColumn, fieldJoinColumn);
                resultMapColumns.addAll(associationColumns);
                resultMapColumns.add(linePrefix + "</association>\n");
                continue;
            }
        }

        return resultMapColumns;
    }
}